package com.peng.mybatis.datasource.pooled;

import cn.hutool.core.lang.Assert;
import lombok.Getter;
import lombok.Setter;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * 池化链接代理
 * <p>
 * 利用代理模式来处理池化连接的各种行为【如关闭，实际上是退还连接进池】
 */
public class PooledConnection implements InvocationHandler {

    public static final String CLOSE = "close";

    public static final Class<?>[] INTERFACES = new Class<?>[]{Connection.class};

    private final int hashcode;

    private final PooledDataSource dataSource;

    @Getter
    private Connection realConnection;

    @Getter
    private Connection proxyConnection;

    @Getter
    @Setter
    private long checkoutTimestamp;

    @Getter
    @Setter
    private long createdTimestamp;

    @Getter
    @Setter
    private long lastUsedTimestamp;

    @Getter
    @Setter
    private int connectionTypeCode;

    private boolean valid;

    public PooledConnection(Connection connection, PooledDataSource dataSource) {
        this.hashcode = connection.hashCode();
        this.realConnection = connection;
        this.dataSource = dataSource;
        this.createdTimestamp = System.currentTimeMillis();
        this.lastUsedTimestamp = System.currentTimeMillis();
        this.valid = true;
        this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), INTERFACES, this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String name = method.getName();
        if (CLOSE.equals(name)) {
            dataSource.pushConnection(this);
            return null;
        } else {
            if (!Object.class.equals(method.getDeclaringClass())) {
                // 除了Object方法，都需要检查连接活性
                checkConnection();
            }
            // 委托给真实连接
            return method.invoke(realConnection, args);
        }
    }

    private void checkConnection() throws SQLException {
        Assert.isTrue(valid, () -> new SQLException("无法访问池化连接，连接已失活"));
    }

    public void invalidate() {
        valid = false;
    }

    public boolean isValid() {
        return valid && realConnection != null && dataSource.pingConnection(this);
    }

    public int getRealHashCode() {
        return realConnection == null ? 0 : realConnection.hashCode();
    }

    public long getTimeElapsedSinceLastUse() {
        return System.currentTimeMillis() - lastUsedTimestamp;
    }

    public long getAge() {
        return System.currentTimeMillis() - createdTimestamp;
    }

    public long getCheckoutTime() {
        return System.currentTimeMillis() - checkoutTimestamp;
    }

    @Override
    public int hashCode() {
        return hashcode;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof PooledConnection) {
            return realConnection.hashCode() == ((PooledConnection) obj).realConnection.hashCode();
        } else if (obj instanceof Connection) {
            return hashcode == obj.hashCode();
        }
        return false;
    }

}
